using System.Reflection;
using System.Text;

namespace Dapper.Bulk;

internal static class SqlGenerator
{
    private static bool IsSimpleType(Type type)
    {
        return type.IsPrimitive ||
               type.IsEnum ||
               type == typeof(string) ||
               type == typeof(decimal) ||
               type == typeof(DateTime) ||
               type == typeof(DateTimeOffset) ||
               type == typeof(TimeSpan) ||
               type == typeof(Guid) ||
               Nullable.GetUnderlyingType(type) != null && IsSimpleType(Nullable.GetUnderlyingType(type)!);
    }

    public static string GenerateInsertSql<T>(EntityMapping<T> mapping) where T : class
    {
        var properties = mapping.Properties
            .Where(p => p != mapping.IdentityProperty && IsSimpleType(p.PropertyType))
            .ToList();
        var columns = string.Join(", ", properties.Select(p => p.Name.ToLower()));
        var parameters = string.Join(", ", properties.Select(p => $"@{p.Name}"));
        
        return $"INSERT INTO {mapping.TableName} ({columns}) VALUES ({parameters})";
    }

    public static string GenerateBatchInsertSql<T>(EntityMapping<T> mapping, int batchSize) where T : class
    {
        var properties = mapping.Properties
            .Where(p => p != mapping.IdentityProperty && IsSimpleType(p.PropertyType))
            .ToList();
        var columns = string.Join(", ", properties.Select(p => p.Name.ToLower()));
        
        var valuesClauses = new List<string>();
        for (int i = 0; i < batchSize; i++)
        {
            var parameters = string.Join(", ", properties.Select(p => $"@{p.Name}{i}"));
            valuesClauses.Add($"({parameters})");
        }
        
        return $"INSERT INTO {mapping.TableName} ({columns}) VALUES {string.Join(", ", valuesClauses)}";
    }

    public static object CreateBatchParameters<T>(EntityMapping<T> mapping, List<T> entities, bool includeIdentity = false) where T : class
    {
        var parameters = new Dictionary<string, object>();
        var properties = mapping.Properties
            .Where(p => IsSimpleType(p.PropertyType) && (includeIdentity || p != mapping.IdentityProperty))
            .ToList();

        for (int i = 0; i < entities.Count; i++)
        {
            var entity = entities[i];
            foreach (var property in properties)
            {
                var value = property.GetValue(entity) ?? DBNull.Value;
                parameters[$"{property.Name}{i}"] = value;
            }
        }

        return parameters;
    }

    public static string GenerateBatchUpdateSql<T>(EntityMapping<T> mapping, int batchSize) where T : class
    {
        if (mapping.IdentityProperty == null)
            throw new InvalidOperationException($"Identity property must be configured for entity {typeof(T).Name}");

        var properties = mapping.Properties
            .Where(p => p != mapping.IdentityProperty && IsSimpleType(p.PropertyType))
            .ToList();

        var updateStatements = new List<string>();
        for (int i = 0; i < batchSize; i++)
        {
            var setClause = string.Join(", ", properties.Select(p => $"{p.Name.ToLower()} = @{p.Name}{i}"));
            updateStatements.Add($"UPDATE {mapping.TableName} SET {setClause} WHERE {mapping.IdentityProperty.Name.ToLower()} = @{mapping.IdentityProperty.Name}{i}");
        }

        return string.Join("; ", updateStatements);
    }

    public static string GenerateBatchDeleteSql<T>(EntityMapping<T> mapping, int idCount) where T : class
    {
        if (mapping.IdentityProperty == null)
            throw new InvalidOperationException($"Identity property must be configured for entity {typeof(T).Name}");

        var idParameters = string.Join(", ", Enumerable.Range(0, idCount).Select(i => $"@id{i}"));
        return $"DELETE FROM {mapping.TableName} WHERE {mapping.IdentityProperty.Name.ToLower()} IN ({idParameters})";
    }

    public static string GenerateUpdateSql<T>(EntityMapping<T> mapping) where T : class
    {
        if (mapping.IdentityProperty == null)
            throw new InvalidOperationException($"Identity property must be configured for entity {typeof(T).Name}");

        var properties = mapping.Properties
            .Where(p => p != mapping.IdentityProperty && IsSimpleType(p.PropertyType))
            .ToList();
        var setClause = string.Join(", ", properties.Select(p => $"{p.Name.ToLower()} = @{p.Name}"));
        
        return $"UPDATE {mapping.TableName} SET {setClause} WHERE {mapping.IdentityProperty.Name.ToLower()} = @{mapping.IdentityProperty.Name}";
    }

    public static string GenerateDeleteSql<T>(EntityMapping<T> mapping) where T : class
    {
        if (mapping.IdentityProperty == null)
            throw new InvalidOperationException($"Identity property must be configured for entity {typeof(T).Name}");

        return $"DELETE FROM {mapping.TableName} WHERE {mapping.IdentityProperty.Name.ToLower()} = @{mapping.IdentityProperty.Name}";
    }

    public static string GenerateSelectSql<T>(EntityMapping<T> mapping) where T : class
    {
        var properties = mapping.Properties.Where(p => IsSimpleType(p.PropertyType)).ToList();
        var columns = string.Join(", ", properties.Select(p => p.Name.ToLower()));
        return $"SELECT {columns} FROM {mapping.TableName}";
    }

    public static string GenerateExistsSql<T>(EntityMapping<T> mapping) where T : class
    {
        if (mapping.IdentityProperty == null)
            throw new InvalidOperationException($"Identity property must be configured for entity {typeof(T).Name}");

        return $"SELECT {mapping.IdentityProperty.Name.ToLower()} FROM {mapping.TableName} WHERE {mapping.IdentityProperty.Name.ToLower()} = @{mapping.IdentityProperty.Name}";
    }
}